import { isPlainObject, isArray } from '../utils'
import { Watcher } from './watcher'
import { Dep } from './dep'


class Observer {
  constructor() {

  }

  walk() {

  }
}


function modifyArrayMethod(arr:unknown[]) {
  const methods = ['push', 'shift', 'pop', 'unshift', 'reverse', 'sort', 'splice']

  const originProto = Array.prototype
  const newProto = Object.create(originProto)

  methods.forEach(method=>{
    Reflect.set(newProto, method, function(this:any, ...args:unknown[]) {
      switch(method) {
        case 'push':
        case 'unshift':
          args = args.map(val=>isPlainObject(val) ? observe(val) : val)
          break
        case 'splice':
          // TODO ...
          break  
      }

      return Reflect.get(originProto, method).call(this, ...args)
    })
  })

  Object.setPrototypeOf(arr, newProto)
}

function defineReactive(target:object, key:string, value:unknown) {
  const dep = new Dep()

  if(isArray(value)) {
    modifyArrayMethod(value)
    value.forEach((val, idx)=>{
      if(isPlainObject(val)) (value as any)[idx] = observe(val as object)
    })
  }

  Object.defineProperty(target, key, {
    get() {
      if(Dep.active) dep.depend()
      return isPlainObject(value) ? observe(value) : value  
    },
    set(newVal) {
      if(newVal === value) return
      value = newVal
      dep.notify()   
    }
  })
}

export function observe<T extends object>(target:T) {
  if(target.hasOwnProperty('__ob__')) {
    return Reflect.get(target, '__ob__') as T
  }

  const dest = {} as T

  Object.defineProperty(target, '__ob__', {
    enumerable:false,
    value: dest
  })

  Object.keys(target).forEach(key=>{
    const originValue = Reflect.get(target, key)
    defineReactive(dest, key, originValue)
  })

  return dest
}

